package me.ingxin.android.router.compiler.processor

import com.squareup.kotlinpoet.*
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
import me.ingxin.android.router.annotaions.App
import me.ingxin.android.router.annotaions.compiler.LComponent
import me.ingxin.android.router.annotaions.compiler.LExtInfo
import me.ingxin.android.router.compiler.util.*
import me.ingxin.android.router.annotaions.route.ComponentMeta
import me.ingxin.android.router.annotaions.route.IComponentWarehouse
import javax.annotation.processing.RoundEnvironment
import javax.lang.model.element.PackageElement

/**
 * Created by ingxin on 2022/4/16
 */
class ComponentAppProcessor(processorUtil: ProcessorUtil, logger: Logger) :
    BaseProcessor(processorUtil, logger) {
    override fun getSupportedAnnotationTypes(): MutableSet<String> {
        return HashSet<String>().apply { add(App::class.java.name) }
    }

    fun checkApp(roundEnv: RoundEnvironment): String? =
        processorUtil.checkApp("ComponentAppProcessor", roundEnv, logger)

    fun process(appOwner: String, component: LComponent?) {
        //检测是否重复生成
        val warehouse = processorUtil.env.elementUtils
            .getTypeElement("${COMPONENT_WAREHOUSE_PACKAGE_NAME}.${COMPONENT_WAREHOUSE_CLASS_NAME}")
        if (warehouse != null) {
            val creator = warehouse.getAnnotation(LExtInfo::class.java)?.creator
            if (appOwner != creator) {
                logger.throwException("Duplicate annotation [@${App::class.java.simpleName}] found in class [$appOwner] and class [$creator]")
            }
        }

        val componentPackage =
            processorUtil.env.elementUtils.getPackageElement(COMPONENT_INDEX_PACKAGE_NAME)
        val allList = if (componentPackage != null) {
            getAllComponent(componentPackage, component)
        } else {
            ArrayList<LComponent>().apply {
                if (component != null) {
                    add(component)
                }
            }
        }

        val componentList = sortDependencies(allList)

        //成员变量
        val property = PropertySpec.builder(
            "components",
            ArrayList::class.asClassName()
                .parameterizedBy(ComponentMeta::class.asClassName()),
            KModifier.PRIVATE
        )
            .initializer("%T()", ArrayList::class)
            .build()

        //init 代码块,用“·”声明不换行空格
        val ibb = CodeBlock.builder()
        componentList.forEach {
            val lastIndex = it.destination.lastIndexOf(".")
            val classPackage = it.destination.substring(0, lastIndex)
            val className =
                it.destination.substring(lastIndex + 1, it.destination.length)
            val componentClass = ClassName(classPackage, className)

            ibb.add(
                "components.add(%T(\"${it.name}\",·%T::class.java,·${it.priority}",
                ComponentMeta::class, componentClass
            )
            if (it.dependencies.isNotEmpty()) {
                ibb.add(",·arrayOf(")
                for ((index, item) in it.dependencies.withIndex()) {
                    ibb.add("\"$item\"")
                    if (index != it.dependencies.size - 1) {
                        ibb.add(",·")
                    }
                }
                ibb.add(")))")
            } else {
                ibb.add("))")
            }
            ibb.add("\n")
        }

        val funcSpec = FunSpec.builder("getComponentList")
            .addModifiers(KModifier.OVERRIDE)
            .returns(
                ArrayList::class.asClassName()
                    .parameterizedBy(
                        ComponentMeta::class.asClassName()
                    )
            )
            .addStatement("return components")
            .build()


        //增加构建信息
        val annotationSpec = AnnotationSpec.builder(LExtInfo::class)
            .addMember(
                CodeBlock.builder()
                    .add("""creator·=·"$appOwner"""").build()
            )
            .build()

        val classSpec = TypeSpec.classBuilder(COMPONENT_WAREHOUSE_CLASS_NAME)
            .addProperty(property)
            .addAnnotation(annotationSpec)
            .addInitializerBlock(ibb.build())
            .addSuperinterface(IComponentWarehouse::class)
            .addFunction(funcSpec)
            .build()

        processorUtil.writeClass(COMPONENT_WAREHOUSE_PACKAGE_NAME, classSpec)

    }

    /**
     * 获取特定包名下所有的组件类
     */
    private fun getAllComponent(
        packageElement: PackageElement,
        currentComponent: LComponent?
    ): MutableList<LComponent> {
        val list = ArrayList<LComponent>()
        val tempMap = HashMap<String, LComponent>()
        currentComponent?.let {
            list.add(it)
            tempMap[it.name] = it
        }

        //遍历包下所有类
        for (element in packageElement.enclosedElements) {
            val annotation = element.getAnnotation(LComponent::class.java) ?: continue
            val name = annotation.name
            val className = annotation.destination
            if (tempMap[name] != null) {
                if (className == tempMap[name]?.destination) {
                    logger.warning("a annotation [@${App::class.java.simpleName}] was added, skip component")
                    continue
                }
                logger.throwException("Duplicate component name [$name] found in class [${tempMap[name]?.destination}] and class [$className]")
            }
            tempMap[name] = annotation
            list.add(annotation)
        }

        tempMap.clear()
        return list
    }

    /**
     * 对所有进行拓扑排序
     */
    private fun sortDependencies(list: MutableList<LComponent>): MutableList<LComponent> {
        if (list.isEmpty()) {
            return list
        }

        //按照priority排序，priority属性越大优先级越高,排序越前
        list.sortWith { a, b -> b.priority - a.priority }

        //DAG图进行拓扑排序
        val dag = DirectedAcyclicGraph<String>()

        //每个组件表示顶点
        val tempMap = HashMap<String, LComponent>()
        list.forEach {
            val name = it.name
            if (tempMap[name] != null) {
                logger.throwException("component [${name}] error, duplicate component name found in ${it.destination} and ${tempMap[name]?.destination}")
            }
            dag.addNode(name)
            tempMap[name] = it
        }

        for (component in list) {
            val name = component.name
            for (depName in component.dependencies) {
                //depName没有在图中，说明没有导入依赖库，则忽略该组件排序
                if (dag.contains(depName)) {
                    dag.addEdge(name, depName)
                } else {
                    logger.warning("can not found component [component name = $depName]")
                }
            }
        }

        try {
            val resList = ArrayList<LComponent>()
            dag.getSortedList().forEach {
                val v = tempMap[it]
                if (v != null) {
                    resList.add(v)
                } else {
                    logger.warning("can not found component [component name = $v]")
                }
            }

            tempMap.clear()
            dag.clear()

            return resList

        } catch (ex: DependenceException) {
            logger.throwException("found cyclic dependencies ${ex.message}")
        }

        return list
    }
}